package com.mindtree.vi.presentation.servlet;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.UndeclaredThrowableException;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.mindtree.vi.business.javaservice.StatusCode;
import com.mindtree.vi.presentation.annotation.ReferencedBy;
import com.mindtree.vi.presentation.annotation.RequestParam;
import com.mindtree.vi.xml.XmlResponse;
import com.mindtree.vi.xml.config.XmlConfigUtil;

public abstract class BaseHttpServlet<Request, Response> extends HttpServlet {

	private Request request;

	private Response response;

	private static boolean registered = false;

	private static final long serialVersionUID = 6984790936657137030L;

	private WebApplicationContext webApplicationContext = null;

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doExecute(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doExecute(req, resp);
	}

	private void processXmlRequest(HttpServletRequest httpRequest,
			Request request) throws IOException, ServletException {
		String xmlString = httpRequest.getParameter("xml");
		Request xmlRequest = null;
		if (xmlString != null && !xmlString.equals("")) {
			xmlRequest = (Request) XmlConfigUtil.fromXml(xmlString);
			setRequest(xmlRequest);
		}
	}

	private void processXmlResponse(HttpServletResponse httpResponse,
			Response response) {
		String xmlStringResponse = XmlConfigUtil.toXml(response);
		if (xmlStringResponse != null && !xmlStringResponse.equals("")) {
			httpResponse.setContentType("text/xml");
			httpResponse.setHeader("Cache-Control",
					"must-revalidate, no-cache, max-age=0, s-maxage=0");
			OutputStream outputStream;
			try {
				outputStream = httpResponse.getOutputStream();
				outputStream.write(xmlStringResponse.getBytes());
				outputStream.flush();
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	private void processXmlResponse(HttpServletResponse httpResponse,
			XmlResponse response) {
		String xmlStringResponse = XmlConfigUtil.toXml(response);
		if (xmlStringResponse != null && !xmlStringResponse.equals("")) {
			httpResponse.setContentType("text/xml");
			httpResponse.setHeader("Cache-Control",
					"must-revalidate, no-cache, max-age=0, s-maxage=0");
			OutputStream outputStream;
			try {
				outputStream = httpResponse.getOutputStream();
				outputStream.write(xmlStringResponse.getBytes());
				outputStream.flush();
				outputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	private synchronized void doExecute(HttpServletRequest httpRequest,
			HttpServletResponse httpResponse) {
		if (!registered) {
			XmlConfigUtil.registerClasses();
			registered = true;
		}
		XmlResponse xmlResponse = null;
		try {
			processAllAnnotations(httpRequest);
			processXmlRequest(httpRequest, request);
			process();/* Process the main business logic here */
		} catch (Throwable e) {
			if(e instanceof UndeclaredThrowableException){
				e = e.getCause();
			}
			xmlResponse = new XmlResponse();
			xmlResponse.setMessage("" + e.getMessage());
			xmlResponse.setStatusCode(StatusCode.COMPILE_FAILURE);
		} finally {
			if (xmlResponse == null) {
				processXmlResponse(httpResponse, response);
			} else {
				processXmlResponse(httpResponse, xmlResponse);
			}
		}
	}

	protected String extractClassName(String code) {
		code = code.trim();
		code = code.substring(code.indexOf("class") + 6, code.indexOf("{"));
		return code + ".java";
	}

	public synchronized Request getRequest() {
		return request;
	}

	public synchronized void setRequest(Request request) {
		this.request = request;
	}

	public synchronized Response getResponse() {
		return response;
	}

	public synchronized void setResponse(Response response) {
		this.response = response;
	}

	public abstract void process();

	private synchronized Object getBean(final String beanId) {
		if (webApplicationContext == null) {
			webApplicationContext = (WebApplicationContext) WebApplicationContextUtils
					.getRequiredWebApplicationContext(getServletContext());
		}

		return webApplicationContext.getBean(beanId);
	}

	private void processAllAnnotations(HttpServletRequest request) {
		Field[] fields = this.getClass().getDeclaredFields();
		for (Field field : fields) {
			try {
				ReferencedBy referencedBy = field
						.getAnnotation(ReferencedBy.class);
				RequestParam requestParam = field
						.getAnnotation(RequestParam.class);
				field.setAccessible(true);
				if (requestParam != null
						&& request.getParameter(requestParam.value()) != null
						&& !request.getParameter(requestParam.value()).equals(
								"")) {
					String string = request.getParameter(requestParam.value());
					Class fieldType = field.getType();
					if (fieldType.equals(String.class)) {
						field.set(this, string);
					} else if (fieldType.equals(Long.class)) {
						field.set(this, Long.valueOf(string));
					} else if (fieldType.equals(Integer.class)) {
						field.set(this, Integer.valueOf(string));
					}
					continue;
				}
				if (referencedBy != null) {
					String beanId = referencedBy.value();
					field.set(this, getBean(beanId));
				}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void init() throws ServletException {
		super.init();
	}

}
